/*
 * Copyright (c) 2010-2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.pivot;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;

import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.HashMap;
import org.apache.pivot.collections.Map;

import org.apache.pivot.util.Filter;
import org.apache.pivot.wtk.Application;
import org.apache.pivot.wtk.DesktopApplicationContext;
import org.apache.pivot.wtk.Display;
import org.apache.pivot.wtk.FileBrowser;
import org.apache.pivot.wtk.FileBrowserSheet;
import org.apache.pivot.wtk.ListButton;
import org.apache.pivot.wtk.MessageType;
import org.apache.pivot.wtk.Prompt;
import org.apache.pivot.wtk.Sheet;
import org.apache.pivot.wtk.SheetCloseListener;
import org.apache.pivot.wtk.Theme;
import org.apache.pivot.wtk.Window;
import org.apache.pivot.wtk.Dialog;
import org.apache.pivot.beans.BXMLSerializer;
import org.apache.pivot.wtk.TabPane;
import org.apache.pivot.wtk.Action;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Label;

import org.apache.pivot.wtk.Alert;
import org.apache.pivot.json.JSON;
import org.apache.pivot.json.JSONSerializer;
import org.apache.pivot.serialization.SerializationException;

import yawning.mew.pivot.feraldruid.FeralDruid;
import yawning.mew.pivot.protwarrior.ProtWarrior;

/**
 * Mew Pivot GUI
 *
 * @author Yawning <yawninglol at gmail.com>
 */
public class MewPivot implements Application, Application.AboutHandler {
	private static MewPivot instance = null;
	private static Window window = null;

	private Display display = null;
	private Dialog aboutMew = null;
	private TabPane tabPane = null;

	private Prompt clazzChoserPrompt = null;
	private Component clazzChoserBody = null;
	private ListButton clazzButton = null;

//	private Component errorDetails = null;
	private Label errorDetailsText = null;

	private final boolean isDebug = true;

	private static File mWorkingDirectory;
	private Filter<File> mDotMewFilter;

	public MewPivot() {
		instance = this; // Save the instance.

		// Setup a "working directory" for the file browser sheets.
		mWorkingDirectory = new File(System.getProperty("user.dir"));

		// Setup my ".mew" file filter.
		mDotMewFilter = new Filter<File>() {
			@Override
			public boolean include(File file) {
				return (file.isFile() && !file.getName().endsWith(".mew"));
			}
		};

		// Setup the menu actions.
		Action.getNamedActions().put("fileNew", new Action() {
			@Override
			public void perform(Component arg0) {
				SheetCloseListener listener = new SheetCloseListener() {
					@Override
					public void sheetClosed(Sheet sheet) {
						if (sheet.getResult()) {
							Prompt prompt = (Prompt) sheet;
							if (prompt.getSelectedOptionIndex() == 0) {
								String clazz = (String) clazzButton.getSelectedItem();
								if (clazz.equals("Feral Druid")) {
									MewPivotModel mewModel = new FeralDruid(instance);
									doNewModel(mewModel);
								} else if (clazz.equals("Protection Warrior")) {
									MewPivotModel mewModel = new ProtWarrior(instance);
									doNewModel(mewModel);
								} else throw new RuntimeException("Unsupported Class");
							}
						}
					}
				};
				clazzChoserPrompt.open(window, listener);
			}
		});

		Action.getNamedActions().put("fileOpen", new Action() {
			@Override
			public void perform(Component arg0) {
				FileBrowserSheet fileBrowserSheet = new FileBrowserSheet(FileBrowserSheet.Mode.OPEN);
				fileBrowserSheet.setRootDirectory(mWorkingDirectory);
				fileBrowserSheet.setDisabledFileFilter(mDotMewFilter);
				fileBrowserSheet.getStyles().put("hideDisabledFiles", true);
				fileBrowserSheet.open(window, new SheetCloseListener() {
					@Override
					public void sheetClosed(Sheet sheet) {
						if (sheet.getResult()) {
							File file = ((FileBrowserSheet) sheet).getSelectedFile();
							setWorkingDirectory(file);

							MewPivotModel existing = getExistingModel(file, null);

							doFileOpen(file, existing);
						}
					}
				});
			}
		});

		Action.getNamedActions().put("fileSave", new Action() {
			@Override
			public void perform(Component arg0) {
				Component tab = tabPane.getSelectedTab();
				if (tab != null) {
					if (getMewModel().getFile() == null) {
						FileBrowserSheet fileBrowserSheet = new FileBrowserSheet(FileBrowserSheet.Mode.SAVE_AS);
						fileBrowserSheet.setRootDirectory(mWorkingDirectory);
						fileBrowserSheet.setDisabledFileFilter(mDotMewFilter);
						fileBrowserSheet.getStyles().put("hideDisabledFiles", true);
						fileBrowserSheet.open(window, new SheetCloseListener() {
							@Override
							public void sheetClosed(Sheet sheet) {
								if (sheet.getResult()) {
									File file = ((FileBrowserSheet) sheet).getSelectedFile();
									setWorkingDirectory(file);

									// Force append .mew to the file.
									if (file.getName().endsWith(".mew") == false) {
										File newFile = new File(file.getParent(), file.getName() + ".mew");
										file = newFile;
									}

									MewPivotModel existing = getExistingModel(file, getMewModel());

									getMewModel().setFile(file);
									doFileSave(existing);
								}
							}
						});
					} else {
						doFileSave(null);
					}
				}
			}
		});

		Action.getNamedActions().put("fileSaveAs", new Action() {
			@Override
			public void perform(Component arg0) {
				Component tab = tabPane.getSelectedTab();
				if (tab != null) {
					FileBrowserSheet fileBrowserSheet = new FileBrowserSheet(FileBrowserSheet.Mode.SAVE_AS);
					fileBrowserSheet.setRootDirectory(mWorkingDirectory);
					fileBrowserSheet.setDisabledFileFilter(mDotMewFilter);
					fileBrowserSheet.getStyles().put("hideDisabledFiles", true);
					fileBrowserSheet.open(window, new SheetCloseListener() {
						@Override
						public void sheetClosed(Sheet sheet) {
							if (sheet.getResult()) {
								File file = ((FileBrowserSheet) sheet).getSelectedFile();
								setWorkingDirectory(file);

								// Force append .mew to the file.
								if (file.getName().endsWith(".mew") == false) {
									File newFile = new File(file.getParent(), file.getName() + ".mew");
									file = newFile;
								}

								doFileWrite(file);

								MewPivotModel existing = getExistingModel(file, getMewModel());
								if (existing != null) doCloseModel(existing);
							}
						}
					});
				}
			}
		});

		Action.getNamedActions().put("fileClose", new Action() {
			@Override
			public void perform(Component arg0) {
				Component tab = tabPane.getSelectedTab();
				if (tab != null) {
					MewPivotModel mewModel = getMewModel();

					doCloseModel(mewModel);
				}
			}
		});

		Action.getNamedActions().put("fileExit", new Action() {
			@Override
			public void perform(Component arg0) {
				DesktopApplicationContext.exit();
			}
		});

		Action.getNamedActions().put("helpAbout", new Action() {
			@Override
			public void perform(Component arg0) {
				aboutRequested();
			}
		});
	}

	private void updateMenubarState() {
		if (tabPane.getTabs().getLength() == 0) {
			Action.getNamedActions().get("fileSave").setEnabled(false);
			Action.getNamedActions().get("fileSaveAs").setEnabled(false);
			Action.getNamedActions().get("fileClose").setEnabled(false);
		} else {
			Action.getNamedActions().get("fileSave").setEnabled(true);
			Action.getNamedActions().get("fileSaveAs").setEnabled(true);
			Action.getNamedActions().get("fileClose").setEnabled(true);
		}
	}

	public static void setWorkingDirectory(File file) {
		mWorkingDirectory = new File(file.getParent());
	}

	public static File getWorkingDirectory() {
		return mWorkingDirectory;
	}

	private void doNewModel(MewPivotModel mewModel) {
		Component tab;

		// Setup the tab.
		tab = mewModel.getTab();
		tab.getUserData().put("Mew", mewModel);

		// Add the new tab to the TabPane.
		tabPane.getTabs().add(tab);
		TabPane.setTabData(tab, mewModel.getTabName());
		tabPane.setSelectedIndex(tabPane.getTabs().getLength() - 1);

		updateMenubarState();
	}

	private MewPivotModel getExistingModel(File file, MewPivotModel mewModel) {
		for (Component tab : tabPane.getTabs()) {
			MewPivotModel tmp = (MewPivotModel) tab.getUserData().get("Mew");

			if (mewModel == null || tmp != mewModel) {
				if (tmp.getFile() != null && tmp.getFile().equals(file)) {
					return tmp;
				}
			}
		}

		return null;
	}

	private void doCloseModel(MewPivotModel mewModel) {
		Component tab = mewModel.getTab();

		mewModel.onClose();
		tab.getUserData().put("Mew", null);
		tabPane.getTabs().remove(tab);
		tabPane.setSelectedIndex(tabPane.getTabs().getLength() - 1);

		updateMenubarState();
	}

	@SuppressWarnings("unchecked")
	private void doFileOpen(File file, MewPivotModel existing) {
		Map<String, Object> backingStore = null;

		JSONSerializer jsonSerializer = new JSONSerializer();

		try {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(file));

				try {
					backingStore = (Map<String, Object>) jsonSerializer.readObject(reader);
				} finally {
					reader.close();
				}
			} catch (FileNotFoundException e) {
				throw new RuntimeException(e);
			} catch (IOException e) {
				throw new RuntimeException(e);
			} catch (SerializationException e) {
				throw new RuntimeException(e);
			}

			// Instantiate the correct model for the class.
			MewPivotModel mewModel = null;
			if (existing == null) {
				try {
					// Backwards compatibility.
//					String className = JSON.get(backingStore, "class");	// XXX: Sigh.
					String className = (String) backingStore.get("class");
					if (className.equals("yawning.mew.pivot.MewPivotFeralDruidCat")) {
						className = "yawning.mew.pivot.feraldruid.FeralDruid";
					}
					int level = JSON.getInt(backingStore, "stat.level");
					if (level == 80) {
						error("Error loading " + file.getName(), "Level 80 support has been deprecated.");
						return;
					}

					Class<?> mewClass = Class.forName(className);
					Constructor<MewPivotModel> mewConstructor = (Constructor<MewPivotModel>) mewClass.getConstructor(MewPivot.class);

					mewModel = (MewPivotModel) mewConstructor.newInstance(instance);
				} catch (ClassNotFoundException e) {
					throw new RuntimeException(e);
				} catch (NoSuchMethodException e) {
					throw new RuntimeException(e);
				} catch (IllegalArgumentException e) {
					throw new RuntimeException(e);
				} catch (InstantiationException e) {
					throw new RuntimeException(e);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(e);
				} catch (InvocationTargetException e) {
					throw new RuntimeException(e);
				}
			} else {
				// XXX: This will break if the person does something like...
				// "Open the file in Mew, then edit the data file with a text editor"
				mewModel = existing;
			}

			// Load the saved parameters.
			mewModel.onOpen(backingStore);

			// Add the model to the display.
			if (existing == null) {
				doNewModel(mewModel);
				mewModel.setFile(file);
				TabPane.setTabData(mewModel.getTab(), mewModel.getFile().getName());
			}
		} catch (RuntimeException e) {
			if (isDebug) {
				e.printStackTrace();
			}
			error("Error loading " + file.getName(), e.getCause().getMessage());
		}
	}

	private void doFileSave(MewPivotModel existing) {
		MewPivotModel mewModel = getMewModel();
		Component tab = tabPane.getSelectedTab();
		File mewFile = mewModel.getFile();

		if (doFileWrite(mewFile)) {
			TabPane.setTabData(tab, mewModel.getFile().getName());
			if (existing != null) {
				doCloseModel(existing);
			}
		}
	}

	private boolean doFileWrite(File mewFile) {
		MewPivotModel mewModel = getMewModel();

		// Initialize the backing store.
		HashMap<String, Object> backingStore = new HashMap<String,Object>();

		try {
			// Save which model we're using, then populate the backing store.
//			JSON.put(backingStore, "class", mewModel.getClass().getName());
			backingStore.put("class", mewModel.getClass().getName());	// XXX: Sigh.
			mewModel.onSave(backingStore);

			// Serialize, and write to file.
			String serializedBackingStore = JSONSerializer.toString(backingStore);
			try {
				BufferedWriter writer = new BufferedWriter(new FileWriter(mewFile));
				try {
					writer.write(serializedBackingStore);
				} finally {
					writer.close();
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		} catch (Exception e) {
			if (isDebug) {
				e.printStackTrace();
			}
			error("Error saving " + mewFile.getName(), e.getCause().getMessage());

			return false;
		}

		return true;
	}

	private void error(String message, String details) {
		if (details != null) {
			errorDetailsText.setText(details);
			Alert.alert(MessageType.ERROR, message, errorDetailsText, window);
		} else {
			Alert.alert(MessageType.ERROR, message, window);
		}
	}

	private MewPivotModel getMewModel() {
		Component tab = tabPane.getSelectedTab();

		if (tab != null) {
			return (MewPivotModel) tab.getUserData().get("Mew");
		}

		return null;
	}

	@Override
	public void startup(Display display, Map<String, String> properties) throws Exception {
		// Replace the default File Browser skin to work around a bug.
		Theme.getTheme().set(FileBrowser.class, yawning.mew.pivot.TerraFileBrowserSkin.class);

		// Load in the GUI definition.
		this.display = display;
		BXMLSerializer bxmlSerializer = new BXMLSerializer();
		window = (Window) bxmlSerializer.readObject(getClass().getResource("MewPivotMain.bxml"));
		tabPane = (TabPane) bxmlSerializer.getNamespace().get("tabPane");

		errorDetailsText = (Label) bxmlSerializer.readObject(getClass().getResource("MewPivotError.bxml"));
		aboutMew = (Dialog) bxmlSerializer.readObject(getClass().getResource("MewPivotAbout.bxml"));

		ArrayList<String> options = new ArrayList<String>();
		options.add("OK");
		options.add("Cancel");

		clazzChoserBody = (Component) bxmlSerializer.readObject(getClass().getResource("ClazzChoser.bxml"));
		clazzButton = (ListButton) bxmlSerializer.getNamespace().get("clazzButton");
		clazzButton.setSelectedIndex(0);

		clazzChoserPrompt = new Prompt(MessageType.QUESTION, "Please select your class:", options, clazzChoserBody);
		clazzChoserPrompt.setTitle("Select Class");
		clazzChoserPrompt.setSelectedOptionIndex(0);

		updateMenubarState();

		// Display the UI and allow for user interaction.
		window.open(this.display);
	}

	@Override
	public boolean shutdown(boolean optional) {
		if (window != null) {
			window.close();
		}

		return false;
	}

	@Override
	public void suspend() {
		// Nothing to do.
	}

	@Override
	public void resume() {
		// Nothing to be done.
	}

	public static void main(String[] args) {
		Locale.setDefault(Locale.US);
		DesktopApplicationContext.main(MewPivot.class, args);
	}

	@Override
	public void aboutRequested() {
		aboutMew.open(display, window);
	}
}
